/*
 * Copyright (C) 2011  rafael1193
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 *
 * Programador: rafael1193, pleonex
 * 
 * Fecha: 16/07/2011
 * 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SDAT
{
    internal static class AdpcmDecompressor
    {
		public static byte[] DecompressADPCM(byte[] data)
		{
			List<byte> resul = new List<byte>();

			#region Preinitialized variables
			int sampHeader = LibNDSFormats.Utils.Read4BytesAsInt32(data, 0);// GetWord(dataOff-4);
			int decompSample = sampHeader & 0xFFFF;
			int stepIndex = (sampHeader >> 16) & 0x7F;
			int[] indexTable = new int[16] { -1, -1, -1, -1, 2, 4, 6, 8,
                                             -1, -1, -1, -1, 2, 4, 6, 8 };

			int[] stepsizeTable = new int[89] { 7, 8, 9, 10, 11, 12, 13, 14,
                                                16, 17, 19, 21, 23, 25, 28,
                                                31, 34, 37, 41, 45, 50, 55,
                                                60, 66, 73, 80, 88, 97, 107,
                                                118, 130, 143, 157, 173, 190, 209,
                                                230, 253, 279, 307, 337, 371, 408,
                                                449, 494, 544, 598, 658, 724, 796,
                                                876, 963, 1060, 1166, 1282, 1411, 1552,
                                                1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026,
                                                4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630,
                                                9493, 10442, 11487, 12635, 13899, 15289, 16818,
                                                18500, 20350, 22385, 24623, 27086, 29794, 32767 };
			int index = stepIndex;
			int stepsize = stepsizeTable[stepIndex];
			#endregion

			data = Bit8ToBit4(data);

			int difference = 0;
			int newSample = decompSample;
			for (int i = 8; i < data.Length; i++)
			{
				difference = 0;

				if ((data[i] & 4) != 0)
					difference += stepsize;
				if ((data[i] & 2) != 0)
					difference += stepsize >> 1;
				if ((data[i] & 1) != 0)
					difference += stepsize >> 2;
				difference += stepsize >> 3;

				if ((data[i] & 8) != 0)
					difference = -difference;
				newSample += difference;

				if (newSample > 32767)
					newSample = 32767;
				else if (newSample < -32768)
					newSample = -32768;

				resul.AddRange(BitConverter.GetBytes((short)newSample));

				index += indexTable[data[i]];
				if (index < 0)
					index = 0;
				else if (index > 88)
					index = 88;
				stepsize = stepsizeTable[index];
			}

			return resul.ToArray();
		}

		public static byte[] DecompressBlock_ADPCM(byte[] datos, int sample = 0, int stepindex = 0)
		{
			List<byte> resul = new List<byte>();

			#region Preinitialized variables
			int[] indexTable = new int[16] { -1, -1, -1, -1, 2, 4, 6, 8,
                                             -1, -1, -1, -1, 2, 4, 6, 8 };

			int[] stepsizeTable = new int[89] { 7, 8, 9, 10, 11, 12, 13, 14,
                                                16, 17, 19, 21, 23, 25, 28,
                                                31, 34, 37, 41, 45, 50, 55,
                                                60, 66, 73, 80, 88, 97, 107,
                                                118, 130, 143, 157, 173, 190, 209,
                                                230, 253, 279, 307, 337, 371, 408,
                                                449, 494, 544, 598, 658, 724, 796,
                                                876, 963, 1060, 1166, 1282, 1411, 1552,
                                                1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026,
                                                4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630,
                                                9493, 10442, 11487, 12635, 13899, 15289, 16818,
                                                18500, 20350, 22385, 24623, 27086, 29794, 32767 };
			int index = stepindex;
			if (index < 0)
				index = 0;
			else if (index > 88)
				index = 88;
			int stepsize = stepsizeTable[index];
			#endregion

			byte[] data = new byte[datos.Length - 4];
			Array.Copy(datos, 4, data, 0, datos.Length - 4);
			data = Bit8ToBit4(data);

			int difference, newSample = sample;
			for (int i = 0; i < data.Length; i++)
			{
				difference = 0;

				if ((data[i] & 4) != 0)
					difference += stepsize;
				if ((data[i] & 2) != 0)
					difference += stepsize >> 1;
				if ((data[i] & 1) != 0)
					difference += stepsize >> 2;
				difference += stepsize >> 3;

				if ((data[i] & 8) != 0)
					difference = -difference;
				newSample += difference;

				if (newSample > 32767)
					newSample = 32767;
				else if (newSample < -32768)
					newSample = -32768;

				resul.AddRange(BitConverter.GetBytes((short)newSample));

				index += indexTable[data[i]];
				if (index < 0)
					index = 0;
				else if (index > 88)
					index = 88;
				stepsize = stepsizeTable[index];
			}

			return resul.ToArray();
		}




        static byte[] Bit8ToBit4(byte[] data)
        {
            List<byte> bit4 = new List<byte>();

            for (int i = 0; i < data.Length; i++)
            {
                bit4.Add((byte)(data[i] & 0x0F));
                bit4.Add((byte)((data[i] & 0xF0) >> 4));
            }

            return bit4.ToArray();
        }

        // As far as I can tell, the NDS IMA-ADPCM format has one difference from standard IMA-ADPCM:
        // it clamps min (and max?) sample values differently (see below).  I really don't know how much of a difference
        // it makes, but this implementation is, to my knowledge, the proper way of doing things for NDS.
        public static byte[] ConvertImaAdpcm(byte[] buf)
        {
            List<byte> o = new List<byte>();
            int dataOff = 4;
            long destOff = 0;
            int sampHeader = BitConverter.ToInt16(buf, dataOff - 4);// GetWord(dataOff - 4);
            int decompSample = sampHeader & 0xFFFF;
            int stepIndex = (sampHeader >> 16) & 0x7F;
            //int decompSample = GetShort(dataOff);
            //int stepIndex = GetShort(dataOff+2);
            long curOffset = dataOff;
            o.AddRange(BitConverter.GetBytes((short)decompSample));
            //destOff++;
            //buf[destOff++] = (short)decompSample;


            byte compByte;
            while (curOffset < buf.Length)
            {
                compByte = buf[curOffset++];
                process_nibble(compByte, ref stepIndex, ref decompSample);
                //Array.Copy(BitConverter.GetBytes((short)decompSample), 0, buf, destOff, 2);
                //destOff++;
                o.AddRange(BitConverter.GetBytes((short)decompSample));
                //((short)buf)[destOff++] = (short)decompSample;
                process_nibble((byte)((compByte & 0xF0) >> 4), ref stepIndex, ref decompSample);
                //Array.Copy(BitConverter.GetBytes((short)decompSample), 0, buf, destOff, 2);
                //destOff++;
                o.AddRange(BitConverter.GetBytes((short)decompSample));
                //((short)buf)[destOff++] = (short)decompSample;
            }
            return o.ToArray();
        }

        // I'm copying nocash's IMA-ADPCM conversion method verbatim.  Big thanks to him. 
        // Info is at http://nocash.emubase.de/gbatek.htm#dssound and the algorithm is described as follows:
        //
        //The NDS data consist of a 32bit header, followed by 4bit values (so each byte contains two values,
        //the first value in the lower 4bits, the second in upper 4 bits). The 32bit header contains initial values:
        //
        //  Bit0-15   Initial PCM16 Value (Pcm16bit = -7FFFh..+7FFF) (not -8000h)
        //  Bit16-22  Initial Table Index Value (Index = 0..88)
        //  Bit23-31  Not used (zero)
        //
        //In theory, the 4bit values are decoded into PCM16 values, as such:
        //
        //  Diff = ((Data4bit AND 7)*2+1)*AdpcmTable[Index]/8      ;see rounding-error
        //  IF (Data4bit AND 8)=0 THEN Pcm16bit = Max(Pcm16bit+Diff,+7FFFh)
        //  IF (Data4bit AND 8)=8 THEN Pcm16bit = Min(Pcm16bit-Diff,-7FFFh)
        //  Index = MinMax (Index+IndexTable[Data4bit AND 7],0,88)
        //
        //In practice, the first line works like so (with rounding-error):
        //
        //  Diff = AdpcmTable[Index]/8
        //  IF (data4bit AND 1) THEN Diff = Diff + AdpcmTable[Index]/4
        //  IF (data4bit AND 2) THEN Diff = Diff + AdpcmTable[Index]/2
        //  IF (data4bit AND 4) THEN Diff = Diff + AdpcmTable[Index]/1
        //
        //And, a note on the second/third lines (with clipping-error):
        //
        //  Max(+7FFFh) leaves -8000h unclipped (can happen if initial PCM16 was -8000h)
        //  Min(-7FFFh) clips -8000h to -7FFFh (possibly unlike windows .WAV files?)

        private static int[] AdpcmTable = new int[89]
{
  0x0007,0x0008,0x0009,0x000A,0x000B,0x000C,0x000D,0x000E,0x0010,0x0011,0x0013,0x0015,
  0x0017,0x0019,0x001C,0x001F,0x0022,0x0025,0x0029,0x002D,0x0032,0x0037,0x003C,0x0042,
  0x0049,0x0050,0x0058,0x0061,0x006B,0x0076,0x0082,0x008F,0x009D,0x00AD,0x00BE,0x00D1,
  0x00E6,0x00FD,0x0117,0x0133,0x0151,0x0173,0x0198,0x01C1,0x01EE,0x0220,0x0256,0x0292,
  0x02D4,0x031C,0x036C,0x03C3,0x0424,0x048E,0x0502,0x0583,0x0610,0x06AB,0x0756,0x0812,
  0x08E0,0x09C3,0x0ABD,0x0BD0,0x0CFF,0x0E4C,0x0FBA,0x114C,0x1307,0x14EE,0x1706,0x1954,
  0x1BDC,0x1EA5,0x21B6,0x2515,0x28CA,0x2CDF,0x315B,0x364B,0x3BB9,0x41B2,0x4844,0x4F7E,
  0x5771,0x602F,0x69CE,0x7462,0x7FFF
};


        private static int[] IMA_IndexTable = new int[8]
{
    -1, -1, -1, -1, 2, 4, 6, 8
};


        private static int IMAMax(int samp) { return (samp > 0x7FFF) ? ((short)0x7FFF) : samp; }
        private static int IMAMin(int samp) { return (samp < -0x7FFF) ? ((short)-0x7FFF) : samp; }
        private static int IMAIndexMinMax(int index, int min, int max) { return (index > max) ? max : ((index < min) ? min : index); }

        private static void process_nibble(byte data4bit, ref int Index, ref int Pcm16bit)
        {
            //int Diff = ((Data4bit & 7)*2+1)*AdpcmTable[Index]/8;
            int Diff = AdpcmTable[Index] / 8;
            if ((data4bit & 1) == 1) Diff = Diff + AdpcmTable[Index] / 4;
            if ((data4bit & 2) == 1) Diff = Diff + AdpcmTable[Index] / 2;
            if ((data4bit & 4) == 1) Diff = Diff + AdpcmTable[Index] / 1;

            if ((data4bit & 8) == 0) Pcm16bit = IMAMax(Pcm16bit + Diff);
            if ((data4bit & 8) == 8) Pcm16bit = IMAMin(Pcm16bit - Diff);
            Index = IMAIndexMinMax(Index + IMA_IndexTable[data4bit & 7], 0, 88);
        }



        private static void clamp_step_index(ref int stepIndex)
        {
            if (stepIndex < 0) stepIndex = 0;
            if (stepIndex > 88) stepIndex = 88;
        }

        private static void clamp_sample(ref int decompSample)
        {
            if (decompSample < -32768) decompSample = -32768;
            if (decompSample > 32767) decompSample = 32767;
        }
    }
}
